﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using TerrainContent;

namespace SAM3D
{
    public static class CDLODCameraExts
    {
        public static IEnumerable<BoundingSphere> GetLODSpheres(this ICamera camera, int LODLevels, float[] LODRanges)
        {
            for (int i = 0; i < LODLevels; i++)
            {
                yield return new BoundingSphere(Vector3.Zero, LODRanges[i]);
            }
        }
    }

    class Terrain : DrawableGameComponent, ITerrainImpl
    {
        private readonly TerrainData _data;
        private List<BoundingSphere> _LODBounds;

        public Terrain(Game game, TerrainData data) : base(game)
        {
            _data = data;
            _cameraProvider = ((ICameraService)Game.Services.GetService(typeof(ICameraService)));
            _LODBounds = new List<BoundingSphere>
                             {
                                 new BoundingSphere(Vector3.Zero, 2),
                                 new BoundingSphere(Vector3.Zero, 4),
                                 new BoundingSphere(Vector3.Zero, 8),
                                 new BoundingSphere(Vector3.Zero, 16),
                                 new BoundingSphere(Vector3.Zero, 32),
                                 new BoundingSphere(Vector3.Zero, 64),
                                 new BoundingSphere(Vector3.Zero, 128),
                                 new BoundingSphere(Vector3.Zero, 256),
                                 new BoundingSphere(Vector3.Zero, 512),
                                 new BoundingSphere(Vector3.Zero, 1024)
                             };
            PickedNodes = new List<TerrainQuadtreeNode>();
            PickedPartialNodes = new List<KeyValuePair<Rectangle, TerrainQuadtreeNode>>();

            UpdateDraw(this, EventArgs.Empty);
            _cameraProvider.GetCamera().Moved += UpdateDraw;

        }

        public float Morph = 0f;
        private Texture2D _ground;
        private Texture2D _heightmap;
        protected override void LoadContent()
        {
            _ground = Game.Content.Load<Texture2D>("earth1");
            _heightmap = _data.Texture;
            GraphicsDevice.RasterizerState = new RasterizerState() { FillMode = FillMode.WireFrame };
            base.LoadContent();
        }

        public void UpdateDraw(object sender, EventArgs e)
        {
            PickedNodes.Clear();
            PickedPartialNodes.Clear();
            _data.RootNode.PickNode(this, _LODBounds.ToArray(), _data.MaxLOD, _cameraProvider.GetCamera().Frustrum,
                                    _cameraProvider.GetCamera().Transform.Position);
        }

        public override void Draw(GameTime time)
        {
            var camera = _cameraProvider.GetCamera();
            var fMeshEffect = _data.FullMesh.Meshes[0].Effects[0];
            var pMeshEffect = _data.QuarterMesh.Meshes[0].Effects[0];
            fMeshEffect.Parameters["GroundTexture"].SetValue(_ground);
            fMeshEffect.Parameters["Heightmap"].SetValue(_heightmap);

            pMeshEffect.Parameters["GroundTexture"].SetValue(_ground);
            pMeshEffect.Parameters["Heightmap"].SetValue(_heightmap);

            fMeshEffect.Parameters["Morph"].SetValue(Morph);
            pMeshEffect.Parameters["Morph"].SetValue(Morph);
            foreach (var node in PickedNodes)
            {
                fMeshEffect.Parameters["LOD"].SetValue(_data.MaxLOD - node.LOD);
                fMeshEffect.Parameters["Color"].SetValue(Color.Red.ToVector4() * node.LOD / 9f);
                fMeshEffect.Parameters["Start"].SetValue(new[] { node.Area.X, node.Area.Y });
                fMeshEffect.Parameters["Dimensions"].SetValue(new[]
                                                                                          {
                                                                                              node.Area.Width,
                                                                                              node.Area.Height
                                                                                          });
                NodeDraw(_data.FullMesh, node.Area, camera);

            }
            foreach (var node in PickedPartialNodes)
            {
                pMeshEffect.Parameters["LOD"].SetValue(_data.MaxLOD - node.Value.LOD);
                pMeshEffect.Parameters["Color"].SetValue(Color.Red.ToVector4() * node.Value.LOD / 9f);
                pMeshEffect.Parameters["Start"].SetValue(new[]
                                                                                     {
                                                                                         node.Key.X,
                                                                                         node.Key.Y
                                                                                     });
                pMeshEffect.Parameters["Dimensions"].SetValue(new[]
                                                                                          {
                                                                                              node.Key.Width,
                                                                                              node.Key.Height
                                                                                          });
                NodeDraw(_data.QuarterMesh, node.Key, camera);
            }
        }
        private void NodeDraw(Model model, Rectangle DrawArea, ICamera camera)
        {
            foreach (ModelMesh mesh in model.Meshes)
            {
                // This is where the mesh orientation is set, as well 
                // as our camera and projection.
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["World"].SetValue(Matrix.CreateScale(DrawArea.Width, 1, DrawArea.Height) *
                                                        Matrix.CreateTranslation(DrawArea.X, 0, DrawArea.Y));
                    effect.Parameters["View"].SetValue(camera.View);
                    effect.Parameters["Projection"].SetValue(camera.Projection);
                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();
            }
        }
        private ICameraService _cameraProvider;

        public ICollection<TerrainQuadtreeNode> PickedNodes { get; private set; }
        public ICollection<KeyValuePair<Rectangle, TerrainQuadtreeNode>> PickedPartialNodes { get; private set; }
    }
}